// fetchWorkingDays.js
// 第3版 @20250529

/*
  输入数据格式
==============================================
  {
    "userName": "殷茂鑫",
    "startTime": "2024-01-23 18:33:51",
    "endTime": null,
    "seatCode": "SUP02",
    "seatRole": "管制",
    "seatPost": "带班主任岗"
  }
==============================================
*/

import dayjs from "dayjs";
import calculateOverlapDuration from "./utils/calculateOverlapDuration.js";
import hasDeepNightTime from "./utils/hasDeepNightTime.js";

const LOG_INFO = false;

// 夜班起止时间, 用于修正夜餐计算时间
// @2025/05/29:
// 此参数与夜餐无关, 但已经不记得为何在 workingDates 计算中使用此参数 ￣□￣｜｜
const NIGHT_START = {
  hour: 21,
  minute: 49,
};

// @2025/05/29:
// 夜餐有效时间范围 (HH:mm:ss)
// dayOffset 是指记在今天(0)还是昨天(1)

const EFFECTIVE_NIGHT_RANGES = [
  {
    start: "22:00:00",
    end: "23:59:59",
    dayOffset: 0,
  },
  {
    start: "00:00:00",
    end: "05:59:59",
    dayOffset: 1,
  },
];

// @2025/08/01
// 夜餐时间
const MIN_NIGHT_SECONDS = 2 * 3600;
const MIN_NIGHT_GROSS_SECONDS = 4 * 3600;

// @2025/05/29:
// 计为 1 天夜餐的时间要求 (秒)
const FULL_NIGHT_LENGTH = 4 * 60 * 60;

// @2025/06/19
// 前、后夜班时段计为 1 天夜餐
// DEEP_NIGHT_POINTS
const DEEP_NIGHT_POINTS = ["02:30", "04:00", "06:00"];

// 2330以后有打卡, 第二天有远郊
const COMMUTING_RESERVE_THRESHOLD = {
  hour: 23,
  minute: 30,
};

// 2230~0920有打卡, 工作时间即为连续
const WORKDAY_CONTINUED_RANGE = {
  start: {
    hour: 22,
    minute: 30,
  },
  end: {
    hour: 9,
    minute: 20,
  },
  remark: "2230-0920之间的打卡计为与次日或前日连续",
};

export default function (data, dateRange, holidays = []) {
  // console.log("fetchWorkingDays", data);
  /**
   * logObj:{
   *   currentIndex:Number,
   *   totalIndex:Number,
   *   progress:Number,
   * }
   */
  // console.log("fetchWorkingDays", dateRange, data);
  if (!data || data.length == 0) {
    return null;
  }
  const result = {};
  const rangeStart = dayjs(dateRange[0]).hour(0).minute(0).second(0);
  const rangeEnd = dayjs(dateRange[1]).hour(23).minute(59).second(59);
  for (let i = 0; i < data.length; i++) {
    // 数据单元
    const cell = data[i];
    const userKey = "user" + cell.userId;
    if (!result[userKey]) {
      // 如果不存在 key 为 userName 的项目, 新建一条人员信息
      result[userKey] = {
        name: cell.userName,
        uid: cell.userId,
        hours: 0, // 总小时
        declared: false,
        details: new Array(), // 打卡明细
        commutingDays: 0, // 远郊天数
        commutingDates: {}, // 远郊日期
        /**
         * com20240201:{
         *   date: Date,
         *   days: Number,
         * }
         */
        overnightDays: 0, // 夜餐天数
        overnightDates: {}, // 夜餐日期
        /**
         * night20240201:{
         *   date: Date,
         *   days: Number,
         *   seconds: Number,
         * }
         */
        workingDays: 0, // 工作天数
        overtimeDays: 0, // 加班天数
        workingDates: {}, // 用于统计每日工作时长
        /**
         * wd20240201:{
         *   date: String,
         *   from: String,
         *   to: String,
         *   duration: Number,
         *   days: Number,
         *   type: Number,
         */
      };
    }

    // 人员目标
    const target = result[userKey];
    const begTime = dayjs(cell.startTime);
    const endTime = cell.endTime ? dayjs(cell.endTime) : dayjs(); // : dayjs(cell.startTime)
    const asHour = cell.asHour;

    // 有效时长: 在统计范围内的席位时间
    const effectiveDuration = computedDuration(
      begTime,
      endTime,
      rangeStart,
      rangeEnd
    );
    // 累加总时长 (有效时长)
    target.hours += asHour ? effectiveDuration : 0;

    // 如果存在申报数据, 且时间在统计范围内, 则标记declared为true
    if (cell.type == 1 && effectiveDuration > 0) {
      // console.log("fetchWorkingDays", cell, effectiveDuration);
      target.declared = true;
    }

    // 探测条件3:
    // 当日打卡跨度小于4H:
    //   且前日18点后有记录: 计1天
    //   否则: 计0.5天
    // 当日打卡跨度大于4H: 计1天

    handleWorkingDates(target, begTime, endTime);

    // 夜餐计算
    handleNightV3(target, begTime, endTime);

    // 探测条件2:
    // 有打卡记录则有远郊
    // COMMUTING_RESERVE_THRESHOLD 后有打卡记录则有次日远郊

    handleCommute(target, begTime);
    handleCommute(target, endTime);

    // console.log("值班统计", "遍历", i + 1, " / ", data.length);
  }

  // 二次遍历: 将 Object 转换为 Array, 统计工作天数/加班天数
  const finalResult = Object.keys(result).map((key) =>
    handleSummary(result[key], rangeStart, rangeEnd, holidays)
  );

  finalResult.sort((a, b) => a.uid - b.uid);

  console.log("值班统计", "已完成");

  if (LOG_INFO) {
    console.log("值班统计", finalResult);
  }

  return finalResult;
}

const handleNightV3 = (target, begTime, endTime) => {
  // 遍历 EFFECTIVE_NIGHT_RANGES
  for (let i = 0; i < EFFECTIVE_NIGHT_RANGES.length; i++) {
    let startTimeString = EFFECTIVE_NIGHT_RANGES[i].start;
    let endTimeString = EFFECTIVE_NIGHT_RANGES[i].end;
    let dayOffset = EFFECTIVE_NIGHT_RANGES[i].dayOffset;
    let seconds = calculateOverlapDuration(
      begTime,
      endTime,
      startTimeString,
      endTimeString
    );
    if (seconds > 0) {
      // 判断与夜班时段存在重叠秒数
      let nightDay = begTime.isSame(endTime, "day")
        ? dayOffset
          ? begTime.subtract(dayOffset, "day")
          : begTime
        : begTime;
      let nightDate = nightDay.format("YYYY-MM-DD");
      let nightKey = "night" + nightDay.format("YYYYMMDD");
      if (!target.overnightDates[nightKey]) {
        // 新建一个 overnightDates 项目
        target.overnightDates[nightKey] = {
          date: nightDate,
          days: 0, // 0, 0.5, 1
          seconds: 0,
          // @2025/06/19
          // 新增属性，用于记录是否为前后半夜
          // 已废除
          isDeepNight: false,
          // @2025/08/01
          // 新增属性，用于按照新规则计算
          grossSeconds: 0,
          timeEarliest: "",
          timeLatest: "",
          proves: [],
        };
      }

      // 已经有整份夜餐,则不进行后续计算
      if (target.overnightDates[nightKey].days == 1) {
        return;
      }

      // 添加验证信息
      target.overnightDates[nightKey].proves.push({
        startTime: begTime.format("MM/DD HH:mm"),
        endTime: endTime.format("MM/DD HH:mm"),
        effectiveHours: (seconds / 3600).toFixed(1),
      });

      // 累加净时间
      target.overnightDates[nightKey].seconds += seconds;

      // 记录最早时间
      if (
        target.overnightDates[nightKey].timeEarliest == "" ||
        begTime.isBefore(target.overnightDates[nightKey].timeEarliest)
      ) {
        let startDateTimeString =
          begTime.format("YYYY-MM-DD") + " " + startTimeString;
        if (begTime.isBefore(startDateTimeString)) {
          target.overnightDates[nightKey].timeEarliest = startDateTimeString;
        } else {
          target.overnightDates[nightKey].timeEarliest = begTime.format(
            "YYYY-MM-DD HH:mm:ss"
          );
        }
      }

      // 记录最晚时间
      if (
        target.overnightDates[nightKey].timeLatest == "" ||
        endTime.isAfter(target.overnightDates[nightKey].timeLatest)
      ) {
        let endDateTimeString =
          endTime.format("YYYY-MM-DD") + " " + endTimeString;
        if (endTime.isAfter(endDateTimeString)) {
          target.overnightDates[nightKey].timeLatest = endDateTimeString;
        } else {
          target.overnightDates[nightKey].timeLatest = endTime.format(
            "YYYY-MM-DD HH:mm:ss"
          );
        }
      }

      // 计算总时长
      target.overnightDates[nightKey].grossSeconds = dayjs(
        target.overnightDates[nightKey].timeLatest
      ).diff(target.overnightDates[nightKey].timeEarliest, "second");

      // 2200-0600期间，有一秒即可拿15
      if (target.overnightDates[nightKey].days == 0) {
        target.overnightDates[nightKey].days = 0.5;
      }

      // 判断是否有机会拿30
      if (target.overnightDates[nightKey].days < 1) {
        if (
          target.overnightDates[nightKey].seconds >= MIN_NIGHT_SECONDS &&
          target.overnightDates[nightKey].grossSeconds >=
            MIN_NIGHT_GROSS_SECONDS
        ) {
          target.overnightDates[nightKey].days = 1;
          console.log(
            `${target.name} 在 ${nightDate} 获得了 1 份夜餐费, 最早有效时间: ${
              target.overnightDates[nightKey].timeEarliest
            }, 最晚有效时间: ${
              target.overnightDates[nightKey].timeLatest
            }}, 净时长: ${(
              target.overnightDates[nightKey].seconds / 3600
            ).toFixed(1)}小时, 毛时长: ${(
              target.overnightDates[nightKey].grossSeconds / 3600
            ).toFixed(1)}小时`
          );
        }
      }
    }
  }
};

const handleCommute = (target, cellTime) => {
  if (!target || !cellTime) {
    return false;
  }
  let matched = false;
  // 当日有打卡则有远郊, 否则申诉
  let keyToday = "com" + cellTime.format("YYYYMMDD");
  if (!target.commutingDates[keyToday]) {
    // 新建Key对应的记录
    target.commutingDates[keyToday] = {
      date: cellTime.format("YYYY-MM-DD"),
      days: 1,
    };
    // target.commutingDays++;
    // 不再计算天数
    matched = true;
    if (LOG_INFO) {
      console.log(
        "值班统计",
        "远郊计算",
        target.name,
        target.commutingDates[keyToday]
      );
    }
  }
  // COMMUTING_RESERVE_THRESHOLD 后有打卡记录则有次日远郊
  if (
    cellTime.hour() > COMMUTING_RESERVE_THRESHOLD.hour ||
    (cellTime.hour() == COMMUTING_RESERVE_THRESHOLD.hour &&
      cellTime.minute() >= COMMUTING_RESERVE_THRESHOLD.minute)
  ) {
    let keyTomorrow = "com" + cellTime.add(1, "day").format("YYYYMMDD");
    if (!target.commutingDates[keyTomorrow]) {
      // 新建 keyTomorrow 对应的记录
      target.commutingDates[keyTomorrow] = {
        date: cellTime.add(1, "day").format("YYYY-MM-DD"),
        days: 1,
      };
      matched = true;
      if (LOG_INFO) {
        console.log(
          "值班统计",
          "远郊计算",
          target.name,
          target.commutingDates[keyTomorrow]
        );
      }
    }
  }
  if (!matched) {
    if (LOG_INFO) {
      // console.log("值班统计", "远郊计算", "已忽略");
    }
  }
  return matched;
};

const handleWorkingDates = (target, begTime, endTime) => {
  let begDayIdent = "wd" + begTime.format("YYYYMMDD");
  let endDayIdent = "wd" + endTime.format("YYYYMMDD");
  let isCrossDate = !endTime.isSame(begTime, "day");

  // 复制新的dayjs对象
  let begTimeSupposed = dayjs(begTime.valueOf());
  let endTimeSupposed = dayjs(endTime.valueOf());

  // 如果席位结束时间在WORKDAY_CONTINUED_RANGE的start之后, 则认为工作结束于23时59分, 不是很有必要
  if (
    endTimeSupposed.hour() > WORKDAY_CONTINUED_RANGE.start.hour ||
    (endTimeSupposed.hour() == WORKDAY_CONTINUED_RANGE.start.hour &&
      endTimeSupposed.minute() >= WORKDAY_CONTINUED_RANGE.start.minute)
  ) {
    if (LOG_INFO)
      console.log("值班统计", "工作日期计算", "已修正结束时间为23:59");
    endTimeSupposed = endTimeSupposed.hour(23).minute(59);
  }

  // 如果席位结束时间在0000以后且在WORKDAY_CONTINUED_RANGE的end之前, 则认为工作结束于end, 解决前半夜下席位的问题
  if (
    endTimeSupposed.hour() < WORKDAY_CONTINUED_RANGE.end.hour ||
    (endTimeSupposed.hour() == WORKDAY_CONTINUED_RANGE.end.hour &&
      endTimeSupposed.minute() <= WORKDAY_CONTINUED_RANGE.end.minute)
  ) {
    if (LOG_INFO)
      console.log(
        "值班统计",
        "工作日期计算",
        "已修正结束时间为" +
          WORKDAY_CONTINUED_RANGE.end.hour +
          ":" +
          WORKDAY_CONTINUED_RANGE.end.minute
      );
    endTimeSupposed = endTimeSupposed
      .hour(WORKDAY_CONTINUED_RANGE.end.hour)
      .minute(WORKDAY_CONTINUED_RANGE.end.minute);
  }

  // 如果席位开始时间在WORKDAY_CONTINUED_RANGE的end之前, 且前一天后打卡记录, 则认为工作开始于0时0分
  let yesterdayIdent = "wd" + begTime.subtract(1, "day").format("YYYYMMDD");
  if (
    begTimeSupposed.hour() < WORKDAY_CONTINUED_RANGE.end.hour ||
    (begTimeSupposed.hour() == WORKDAY_CONTINUED_RANGE.end.hour &&
      begTimeSupposed.minute() <= WORKDAY_CONTINUED_RANGE.end.minute)
  ) {
    if (target.workingDates[yesterdayIdent]) {
      if (LOG_INFO)
        console.log(
          "值班统计",
          "工作日期计算",
          target.name,
          begTimeSupposed.format("YYYY-MM-DD"),
          "已修正开始时间为00:00"
        );
      begTimeSupposed = begTimeSupposed.hour(0).minute(0);
    } else {
      if (LOG_INFO)
        console.log(
          "值班统计",
          "工作日期计算",
          target.name,
          begTimeSupposed.format("YYYY-MM-DD"),
          "未修正, 因未检索到前一日记录"
        );
    }
  }

  if (!target.workingDates[begDayIdent]) {
    // 如果不存在上席位日期对应的元素, 则新建元素, 起始/结束 时间为当前 cell 的起止时间
    target.workingDates[begDayIdent] = {
      date: begTime.format("YYYY-MM-DD"),
      from: begTimeSupposed.format("YYYY-MM-DD HH:mm:ss"),
      to: isCrossDate
        ? begTimeSupposed.format("YYYY-MM-DD") + " 23:59:59"
        : endTimeSupposed.format("YYYY-MM-DD HH:mm:ss"),
      // 如果跨日期, 则结束时间为上席位日期的 23:59:59, 否则即为下席位时间
      duration: 0,
      days: 0.5,
      type: 0,
    };
  } else {
    // 已经存在上席位日期对应的元素
    if (
      begTimeSupposed.isBefore(target.workingDates[begDayIdent].from, "minute")
    ) {
      target.workingDates[begDayIdent].from = begTimeSupposed.format(
        "YYYY-MM-DD HH:mm:ss"
      );
    }
    if (isCrossDate) {
      target.workingDates[begDayIdent].to =
        begTimeSupposed.format("YYYY-MM-DD") + " 23:59:59";
    } else if (
      endTimeSupposed.isAfter(target.workingDates[begDayIdent].to, "minute")
    ) {
      target.workingDates[begDayIdent].to = endTimeSupposed.format(
        "YYYY-MM-DD HH:mm:ss"
      );
    }
  }
  // 修改起始日期对应元素的 duration
  // 也可以在二次遍历时进行计算, 以提高计算速度
  target.workingDates[begDayIdent].duration = dayjs(
    target.workingDates[begDayIdent].to
  ).diff(target.workingDates[begDayIdent].from, "hour", true);
  // console.log("test", i, target);
  // 跨度大于4小时, 则执勤时间为1天
  if (target.workingDates[begDayIdent].duration >= 4) {
    target.workingDates[begDayIdent].days = 1;
  }

  // 跨日期的情况, 修改次日对应元素的内容
  if (isCrossDate) {
    // 如果不存在下席位日期对应的元素, 新建元素
    if (!target.workingDates[endDayIdent]) {
      target.workingDates[endDayIdent] = {
        date: endTimeSupposed.format("YYYY-MM-DD"),
        from: endTimeSupposed.format("YYYY-MM-DD") + " 00:00:00", // 起始时间为结束时间当日0点
        to: endTimeSupposed.format("YYYY-MM-DD HH:mm:ss"), // 结束时间为下席位时间
        duration: 0,
        days: 0.5,
        type: 0,
      };
    } else {
      // 已经存在下席位日期对应的元素
      target.workingDates[endDayIdent].from =
        endTimeSupposed.format("YYYY-MM-DD") + " 00:00:00";
      if (
        endTimeSupposed.isAfter(target.workingDates[endDayIdent].to, "minute")
      ) {
        target.workingDates[endDayIdent].to = endTimeSupposed.format(
          "YYYY-MM-DD HH:mm:ss"
        );
      }
    }
    // 跨日期的情况, 修改下席位日期对应元素的 duration
    // 也可以在二次遍历时进行计算, 以提高计算速度
    target.workingDates[endDayIdent].duration = dayjs(
      target.workingDates[endDayIdent].to
    ).diff(target.workingDates[endDayIdent].from, "hour", true);
    // 跨度大于4小时, 则执勤时间为1天
    if (target.workingDates[endDayIdent].duration > 4) {
      target.workingDates[endDayIdent].days = 1;
    }
  }

  // 当日真实的打卡跨度;
  let realBegTime = dayjs(begTime.valueOf());
  let realEndTime = dayjs(endTime.valueOf());

  // 20240630:确认是否存在夜间小时(>NIGHT_START)
  let isInNight =
    (begTimeSupposed.hour() >= NIGHT_START.hour &&
      begTimeSupposed.minute() >= NIGHT_START.minute) ||
    (endTimeSupposed.hour() >= NIGHT_START.hour &&
      endTimeSupposed.minute() >= NIGHT_START.minute);
  if (!target.workingDates[begDayIdent]["isInNight"]) {
    target.workingDates[begDayIdent]["isInNight"] = isInNight;
  }
  if (isCrossDate && !target.workingDates[endDayIdent]["isInNight"]) {
    target.workingDates[endDayIdent]["isInNight"] = isInNight;
  }
  // 更新真实起止时间
  // if (isCrossDate) {
  // }
  if (LOG_INFO)
    console.log(
      "值班统计",
      "工作日期计算",
      target.name,
      target.workingDates[begDayIdent],
      target.workingDates[endDayIdent]
    );
};

const handleSummary = (element, rangeStart, rangeEnd, holidays) => {
  if (LOG_INFO) console.log("值班统计", "统计", element.name, element.uid);

  let workingDates = Object.keys(element.workingDates)
    .map((wdk) => {
      let inRange =
        rangeStart.isSameOrBefore(element.workingDates[wdk].date, "day") &&
        rangeEnd.isSameOrAfter(element.workingDates[wdk].date, "day");
      let isHoliday = holidays.includes(element.workingDates[wdk].date);
      return {
        date: element.workingDates[wdk].date,
        from: element.workingDates[wdk].from,
        to: element.workingDates[wdk].to,
        duration: element.workingDates[wdk].duration,
        days: inRange ? element.workingDates[wdk].days : 0,
        type: isHoliday ? 1 : 0,
      };
    })
    .filter((item) => item.days > 0);

  let commutingDates = Object.keys(element.commutingDates)
    .map((cdk) => {
      return element.commutingDates[cdk];
    })
    .filter(
      (cell) =>
        rangeStart.isSameOrBefore(cell.date, "day") &&
        rangeEnd.isSameOrAfter(cell.date, "day")
    );

  let overnightDates = Object.keys(element.overnightDates)
    .map((odk) => {
      // console.log("overnightDates", element.name, element.overnightDates[odk]);
      return element.overnightDates[odk];
    })
    .filter(
      (cell) =>
        rangeStart.isSameOrBefore(cell.date, "day") &&
        rangeEnd.isSameOrAfter(cell.date, "day")
    );

  let overtimeDates = workingDates.filter((element) => element.type == 1);
  return {
    name: element.name,
    uid: element.uid,
    hours: Math.round(element.hours * 100) / 100,
    commutingDates,
    // commutingDays: commutingDates.length,
    // commutingDays: element.commutingDays,
    commutingDays: summaryDays(commutingDates),
    overnightDates,
    // overnightDays: overnightDates.length,
    // overnightDays: element.overnightDays,
    overnightDays: summaryDays(overnightDates),
    workingDates,
    // workingDays: workingDates.length,
    workingDays: summaryDays(workingDates),
    overtimeDates,
    // overtimeDays: overtimeDates.length,
    overtimeDays: summaryDays(overtimeDates),
    declared: element.declared,
  };
};

const summaryDays = (dateArray) => {
  let days = 0;
  for (let i = 0; i < dateArray.length; i++) {
    days += dateArray[i].days;
  }
  return days;
};

const computedDuration = (begTime, endTime, rangeStart, rangeEnd) => {
  let isEndTimeInRange = endTime.isBefore(rangeEnd);
  let isBegTimeInRange = begTime.isAfter(rangeStart);
  let effectiveEndTime = isEndTimeInRange ? endTime : rangeEnd;
  let effectiveBegTime = isBegTimeInRange ? begTime : rangeStart;
  let effectiveDuration = effectiveEndTime.diff(effectiveBegTime, "hour", true);
  effectiveDuration = effectiveDuration > 0 ? effectiveDuration : 0;
  return effectiveDuration;
};
